package nayasis.common.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.DatatypeConverter;


/**
 * 문자열 처리용 유틸리티 클래스
 *
 * @author 	nayasis
 */
public abstract class StringUtil {

	/**
	 * 문자열이 비어있는지 여부를 확인한다.
	 *
	 * <pre>
	 * 1. 문자열이 null 이면 true
	 * 2. 문자열이 "" 이면 true
	 * 3. 문자열이 빈칸만으로 구성되어 있어도 true
	 * </pre>
	 *
	 * @param string 검사할 문자열
	 * @return 비어있는지 여부
	 */
	public static boolean isEmpty( Object string ) {

		return string == null || string.toString().trim().length() == 0;

	}

	/**
	 * 문자열을 주어진 포맷에 맞춰 출력한다.
	 *
	 * 포맷은 '@' 문자를 치환가능문자로 사용하며,
	 * '@' 문자를 그대로 출력하고 싶을 경우 '\\@'이라고 적어주어야 한다.
	 *
	 * <pre>
	 * StringUtil.format( "@는 사람입니다.", "박태민" ); --> "박태민은 사람입니다."
	 * StringUtil.format( "@는 사람입니다.", "ABC"    ); --> "ABC는 사람입니다."
	 * StringUtil.format( "@는 사람입니다." );           --> "는 사람입니다."
	 * StringUtil.format( "사람입니다." );               --> "사람입니다."
	 * </pre>
	 *
	 * @param format 문자열 포맷
	 * @param param  '@' 문자를 치환할 파라미터
	 * @return 포맷에 맞는 문자열
	 */
	public static String format( String format, Object... param ) {

		if( isEmpty(format) ) return "";

		int paramLength = param.length;
		int paramIndex  = 0;

		StringBuffer cf = new StringBuffer();

		for( int i = 0, iCnt = format.length(); i < iCnt; i++ ) {

			char c = format.charAt( i );

			if( c == '@' ) {

				// "\\@"으로 입력될 경우 '@'문자를 치환하지 않고 그대로 출력
				if( i > 0 && format.charAt(i-1) == '\\' ) {
					cf.deleteCharAt( cf.length() - 1 );
					cf.append( c );
					continue;

				// 치환할 파라미터가 없다면 '@' 문자를 '' 로 출력
				} else if( paramLength == 0 || paramIndex == paramLength ) {
					continue;

				// '@' 문자를 파라미터로 치환
				} else {

					String paramToAppend = ( param[ paramIndex ] == null ) ? "null" : param[ paramIndex ].toString();

					// 파라미터가 한글이고 받침이 있을 경우
					// [은/는,이/가, 을/를] 변경처리 수행
					char[] lastChar = getLastKrChar( paramToAppend );
					if( lastChar.length == 3 && (i - 1 < iCnt) ) {

						char nextC      = format.charAt( i + 1 );
						char nextAppend = ' ';

						boolean hasThirdChar = ( lastChar[2] != '\0' );

						switch( nextC ) {

							case '은' : case '는' :
								nextAppend = hasThirdChar ? '은' : '는';
								break;
							case '이' : case '가' :
								nextAppend = hasThirdChar ? '이' : '가';
								break;
							case '을' : case '를' :
								nextAppend = hasThirdChar ? '을' : '를';
								break;

						}

						cf.append( paramToAppend );

						if( nextAppend != ' ' ) {
							cf.append( nextAppend );
							i++;
						}

					// 파라미터가 한글이 아니거나, 또는 한글이지만 받침이 없을 경우
					} else {
						cf.append( paramToAppend );
					}

					paramIndex++;
				}

			} else {

				cf.append( c );
			}

		}

		// 결과값 반환
		return cf.toString();

	}

    /**
     *
     * 입력한 문자열 앞뒤에  특정문자를 Left Padding한 문자열을 반환한다.
     *
     * <pre>
     *
     * [사용 예제]
     *
     * lpad("AAAAAA", 'Z', 10) ) ===> ZZZZAAAAAA
     *
     * </pre>
     *
     * @param value    	조작할 문자열
     * @param padChar	PADDING 문자
     * @param length	결과 문자열 길이
     * @return String Padding 된 문자열
     */
    public static String lpad( String value, char padChar, int length ) {

        int valueCharCnt = ( value == null ) ? 0 : value.length();
        int index        = Math.max( length - valueCharCnt, 0 );
        
        char[] result = new char[ length ];
        
        for( int i = 0; i < index; i++ ) {
            result[ i ] = padChar;
        }

        for( int i = 0, iCnt = Math.min(length, valueCharCnt); i < iCnt; i++ ) {
            
            result[ index + i ] = value.charAt( i );
            
        }
        
        return new String( result );

    }

    /**
     *
     * 입력한 문자열 앞뒤에  특정문자를 Right Pading한 문자열을 반환한다.
     *
     * <pre>
     *
     * [사용 예제]
     *
     * rpad("AAAAAA", 'Z', 10) )    ===> AAAAAAZZZZ
     *
     * </pre>
     *
     * @param value    	조작할 문자열
     * @param padChar	PADDING 문자
     * @param length	결과 문자열 길이
     * @return String Padding 된 문자열
     */
    public static String rpad( String value, char padChar, int length ) {

        int valueCharCnt = ( value == null ) ? 0 : value.length();
        int index        = Math.min( length, valueCharCnt );
        
        char[] result = new char[ length ];
        
        for( int i = 0; i < index; i++ ) {
            result[ i ] = value.charAt( i );
        }

        for( int i = index; i < length; i++ ) {
            result[ i ] = padChar;
        }

        
        return new String( result );
        
    }


    /**
     * 입력값이 null일 경우 공백문자로 치환받는다.
     * 
     * @param val 입력값
     * @return NVL 문자열
     */
    public static String nvl( Object val ) {
    	if( val == null ) {
    		return  "";
    	} else {
    		return val.toString();
    	}
    }
    
    /**
     * 입력값이 null일 경우, 지정한 문자로 치환받는다.
     * 
     * @param val 입력값
     * @param nvlValue 지정한 문자
     * @return NVL 문자열
     */
    public static String nvl( Object val, Object nvlValue ) {
    	
    	if( val == null ) {
    		return  nvl( nvlValue );
    	} else {
    		return val.toString();
    	}
    	
    }
    

    /**
     * 문자열을 Camel 형으로 변환한다.
     * <pre>
     * String text = DataConverter.getCamel( "unicode_text" );
     * System.out.println( text ); --> "unicodeText" 가 출력됨
     * </pre>
     * @param param     변환할 문자열
     * @return CAMEL 형 문자열
     * @author 정화수
     */
    public static String getCamel( String param ) {
    	
    	if( isEmpty(param) ) return "";

    	param = param.toLowerCase();
        Pattern pattern = Pattern.compile( "(_[a-zA-Z])" );
        Matcher matcher = pattern.matcher( param );
        StringBuffer sb = new StringBuffer();

        while( matcher.find() ) {

            String r = matcher.group().substring( 1 );
            if( matcher.start() != 0 ) r = r.toUpperCase();

            matcher.appendReplacement( sb, r );

        }

        matcher.appendTail( sb );

        return sb.toString();

    }

    /**
     * CAMEL형 문자열을 _ 형 문자열로 변환한다.
     * <pre>
     * String text = DataConverter.getUnCamel( "unicodeText" );
     * System.out.println( text ); --> "unicode_text" 가 출력됨
     * </pre>
     * @param param     변환할 문자열
     * @return UNDERLINE 형 문자열
     * @author 정화수
     * @since 2011-05-02
     */
    public static String getUnCamel( String param ) {

        Pattern pattern = Pattern.compile( "([A-Z])" );
        Matcher matcher = pattern.matcher( param );
        StringBuffer sb = new StringBuffer();

        while( matcher.find() ) {

            if( matcher.start() == 0 ) continue;

            String r = matcher.group();
            matcher.appendReplacement( sb, "_" + r.toLowerCase() );

        }

        matcher.appendTail( sb );

        return sb.toString();

    }



    /**
     * JSON 구조가 깨지지 않게끔 텍스트를 가공한다.
     * @param param
     * @return 특수문자가 제거된 JSON 텍스트
     * @author 정화수
     * @since 2011-04-07
     */
    public static String escapeJson( Object param ) {

         if( param == null ) return "";

         String tempTxt = param.toString();

         StringBuffer sb = new StringBuffer();

         for( int inx = 0, inxCnt = tempTxt.length(); inx < inxCnt; inx++ ) {

             char ch = tempTxt.charAt( inx );

             switch(ch){
                 case '"':  sb.append("\\\""); break;
                 case '\\': sb.append("\\\\"); break;
                 case '\b': sb.append("\\b");  break;
                 case '\f': sb.append("\\f");  break;
                 case '\n': sb.append("\\n");  break;
                 case '\r': sb.append("\\r");  break;
                 case '\t': sb.append("\\t");  break;
                 case '/':  sb.append("\\/");  break;
                 default:
                     if( ch >= '\u0000' && ch <= '\u001F' ) {
                         String ss = Integer.toHexString(ch);
                         sb.append("\\u");
                         for(int k = 0; k < 4-ss.length(); k++){
                             sb.append('0');
                         }
                         sb.append(ss.toUpperCase());
                     } else {
                         sb.append(ch);
                     }
             }
         }

         return sb.toString();

     }

    /**
     * \\ 문자를 \ 문자로 변경해 텍스트를 원복시킨다.
     * @param param
     * @return 가공된 문자가 원복된 텍스트
     * @author 정화수
     * @since 2011-10-13
     */
    public static String unescapeJson( Object param ) {

    	if( param == null ) return "";

    	String srcTxt = param.toString();

    	int len = srcTxt.length();

    	StringBuffer sb = new StringBuffer( len );

    	for( int i = 0; i < len - 1; i++ ) {

    		char c = srcTxt.charAt( i );

    		if( c == '\\' ) {

    			char nc = srcTxt.charAt( i + 1 );

    			switch( nc ) {

	    			case 'b' : sb.append( '\b' ); i++; continue;
	    			case 'f' : sb.append( '\f' ); i++; continue;
	    			case 'n' : sb.append( '\n' ); i++; continue;
	    			case 'r' : sb.append( '\r' ); i++; continue;
	    			case 't' : sb.append( '\t' ); i++; continue;

    			}

    		}

    		sb.append( c );

    	}

    	sb.append( srcTxt.charAt(len-1) );

    	return sb.toString();
    }

    /**
     * 문자열의 마지막 글자가 한글일 경우 초성/중성/종성이 분리되어 담긴 배열을 구한다.
     *
     * <pre>
     *
     * StringUtil.getLastKrChar( "메롱" ); -> [ 'ㄹ','ㅗ','ㅇ'] 을 반환
     * StringUtil.getLastKrChar( "정화수" ); -> ['ㅅ','ㅜ','\0' ] 을 반환
     * StringUtil.getLastKrChar( "메롱Han" ); -> null 을 반환
     *
     * </pre>
     *
     * @param string 검사할 문자열
     * @return 한글일 경우 초성/중성/종성 순으로 문자가 분리된 배열, 한글이 아닐 경우 길이가 0인 char 배열을 반환
     * @author 정화수
     * @since 2011-06-20
     */
    public static char[] getLastKrChar( String string ) {

    	if( string == null || string.length() == 0 ) return new char[0];

    	char[] HAN_1ST = new char[] { 'ㄱ','ㄲ','ㄴ','ㄷ','ㄸ','ㄹ','ㅁ','ㅂ','ㅃ','ㅅ','ㅆ','ㅇ','ㅈ','ㅉ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ' };
    	char[] HAN_2ND = new char[] { 'ㅏ','ㅐ','ㅑ','ㅒ','ㅓ','ㅔ','ㅕ','ㅖ','ㅗ','ㅘ','ㅙ','ㅚ','ㅛ','ㅜ','ㅝ','ㅞ','ㅟ','ㅠ','ㅡ','ㅢ','ㅣ' };
    	char[] HAN_3RD = new char[] { '\0','ㄱ','ㄲ','ㄳ','ㄴ','ㄵ','ㄶ','ㄷ','ㄹ','ㄺ','ㄻ','ㄼ','ㄽ','ㄾ','ㄿ','ㅀ','ㅁ','ㅂ','ㅄ','ㅅ','ㅆ','ㅇ','ㅈ','ㅊ','ㅋ','ㅌ','ㅍ','ㅎ' };

    	char lastChar = string.charAt( string.length() - 1 );

    	if( lastChar < 0xAC00 || lastChar > 0xD79F ) return new char[0];

    	lastChar -= 0xAC00;

    	int idx3rd = lastChar % 28;
    	int idx2nd = ( (lastChar - idx3rd) / 28 ) % 21;
    	int idx1st = (int) ( ((lastChar - idx3rd) / 28) / 21 );

    	char[] result = new char[3];

    	result[0] = HAN_1ST[ idx1st ];
    	result[1] = HAN_2ND[ idx2nd ];
    	result[2] = HAN_3RD[ idx3rd ];

    	return result;

    }

    /**
     * <pre>
     *
     * 전화번호에 지역번호, 국번, 번호를 구분해 -를 붙여서 return 해주는 메소드
     *
     * </pre>
     * @param phoneNumber 전화번호
     * @return - 기호로 구분된 전화번호
     * @author nayasis
     * @since  2011-08-09
     */
    public static String getPhoneNumber( String phoneNumber ) {

    	if( phoneNumber == null ) return "";

    	return phoneNumber.replaceAll( "\\D*(02|\\d{3})\\D*(\\d{3,4})\\D*(\\d{4})", "$1-$2-$3" );

    }

    /**
     * 문자열의 첫글자를 소문자로 바꾼다.
     *
     * @param text 처리할 문자열
     * @return 변환된 문자열
     */
    public static String lowerFirst( String text ) {
        if( text == null || text.length() < 1 ) return text;
        return ( text.substring(0, 1).toLowerCase() + text.substring(1) );
    }

    /**
     * 문자열의 첫글자를 대문자로 바꾼다.
     *
     * @param text 처리할 문자열
     * @return 변환된 문자열
     */
    public static String upperFirst( String text ) {
        if( text == null || text.length() < 1 ) return text;
        return ( text.substring(0, 1).toUpperCase() + text.substring(1) );
    }

    /**
     * 객체를 텍스트로 encode 한다.
     *
     * @param vo 텍스트로 만들 객체
     * @return encode된 텍스트
     * @throws IOException
     */
    public static String encode( Object vo ) {

    	String result = null;
    	
    	try (
    		ByteArrayOutputStream bos = new ByteArrayOutputStream();
    		ObjectOutputStream    oos = new ObjectOutputStream( bos );
    			
    	) {
    		oos.writeObject( vo );
    		result = DatatypeConverter.printBase64Binary( bos.toByteArray() );
    		
    	} catch (IOException e) {
			e.printStackTrace();
		}
    	
        return result;

    }

    /**
     * 텍스트를 객체로 decode 한다.
     *
     * @param text 객체로 만들 text
     * @return decode된 객체
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static Object decode( String text ) {

    	byte o[] = DatatypeConverter.parseBase64Binary( text );

        Object vo = null;

    	try (
    		ByteArrayInputStream bis = new ByteArrayInputStream( o );
    		ObjectInputStream    ois = new ObjectInputStream( bis );
    			
    	) {
    		vo = ois.readObject();
    		
    	} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
    	
        return  vo;

    }

    /**
	 * 문자열에서 숫자만 추출한다.
	 *
	 * @param string 작업할 대상 문자열
	 * @return 숫자만 추출된 문자열
	 */
	public static String getNum( String string ) {

		if( string == null || "".equals(string) ) return "";

		return string.replaceAll( "[^0-9]", "" );

	}

	/**
	 * 에러를 문자열로 만든다.
	 *
	 * @param exception 에러
	 * @return 문자열
	 */
	public static StringBuffer getStackTrace( Throwable exception ) {

		StringBuffer sb = new StringBuffer();

		if( exception == null ) return sb;

	    StackTraceElement[] stackTrace = exception.getStackTrace();

		sb.append( exception ).append( '\n' );

		for( StackTraceElement trace : stackTrace ) {
			sb.append( "\tat " ).append( trace ).append('\n');
		}

	    Throwable cause = exception.getCause();

	    if( cause != null ) {

	    	sb.append( "Caused By : " );
	        sb.append( getStackTrace(cause) );
	    }

	    return sb;

	}

}
